﻿using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using AForge.Video;
using AForge.Video.FFMPEG;
using AForge.Vision.Motion;

namespace System.Camera.Monitor
{
    public class ipCameraMonitor : IDisposable
    {
        #region Properties
        // beep and autorecordstop properties
        public Int32 BeepFreq { get; set; }
        public Int32 BeepDura { get; set; }
        public Int32 AutoRecordStop { get; set; }
        public Int32 AutoNotifyStop { get; set; }
        public string URL { get; set; }

        // different option toggles
        public bool RecordOnMotion = false;
        public bool BeepOnMotion = false;
        public bool MotionDetection = false;
        public bool forceRecord = false;
        #endregion

        #region Declarations
        MotionDetector motiondetect;
        private IVideoSource ivideoSource = null;
        // a reference to the PictureBox on the CameraIPForm
        PictureBox display;

        // was there any motion detected previously
        bool motionDetected = false;
        // counter used delay/skip frames from being processed by the MotionDetector
        int calibrateAndResume = 0; 

        // output video resolution info
        bool isResolutionSet = false;
        int Width = 0;
        int Height = 0;

        // recording flag
        public bool IsRecording = false;

        // Queue that stores frames to be written by the recorder thread
        Queue<Bitmap> frames = new Queue<Bitmap>();
        // default recording path
        public string RecordingPath = "recording";

        // used to display message on the main form
        int showRecordMarkerCount = 0;
        // value to check if there are motion on the image
        private float motionAlarmLevel = 0.015f; 
        #endregion

        #region Methods
        /// <summary>
        /// Initializes a new instance of the <see cref="ipCameraMonitor"/> class.
        /// </summary>
        /// <param name="videoSource">The video source.</param>
        /// <param name="videoPlayer">The video player.</param>
        /// <param name="picBox">The pic box.</param>
        public ipCameraMonitor(IVideoSource videoSource, VideoSourcePlayer videoPlayer, PictureBox picBox)
        {
            motiondetect = new MotionDetector(new TwoFramesDifferenceDetector(), null); // creates the motion detector
            
            // close previous video source
            CloseVideoSource(videoPlayer);

            // start new video source
            videoPlayer.VideoSource = new AsyncVideoSource(videoSource);
            videoPlayer.Start();
            videoPlayer.NewFrame += new VideoSourcePlayer.NewFrameHandler(videoPlayer_NewFrame);
            videoPlayer.Paint += new PaintEventHandler(DrawMessage);
            
            // mimic the video on videoPlayer; for broadcasting video
            display = picBox;
            display.Paint += new PaintEventHandler(DrawMessage);

            ivideoSource = videoSource;
        }

        /// <summary>
        /// Stops the capture.
        /// </summary>
        public void StopCapture()
        {
            if (this.ivideoSource.IsRunning)
            {
                // we must stop the IVideoSource when done to free it so it can be used by other applications
                this.ivideoSource.Stop();
            }
        }

        /// <summary>
        /// Closes the video source.
        /// </summary>
        /// <param name="videoPlayer">The video player.</param>
        private void CloseVideoSource(VideoSourcePlayer videoPlayer)
        {

            // stop current video source
            videoPlayer.SignalToStop();

            // wait for the current video source to stop
            videoPlayer.WaitForStop();

            // wait 2 seconds until camera stops
            for (int i = 0; (i < 50) && (videoPlayer.IsRunning); i++)
            {
                Thread.Sleep(100);
            }

            if (videoPlayer.IsRunning)
                videoPlayer.Stop();

            // reset motion detector
            if (motiondetect != null)
                motiondetect.Reset();

            videoPlayer.BorderColor = Color.Black;
        }

        /// <summary>
        /// Draws the message.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="PaintEventArgs"/> instance containing the event data.</param>
        private void DrawMessage(object sender, PaintEventArgs e)
        {
            using (Font myFont = new Font("Tahoma", 10, FontStyle.Regular))
            {
                e.Graphics.DrawString(DateTime.Now.ToString() + ((this.motionDetected) ? " [MOTION DETECTED]" : ""), myFont, ((this.motionDetected) ? Brushes.Red : Brushes.Green), new Point(15, 15));
                if (this.IsRecording)
                {
                    if (this.showRecordMarkerCount > 10)
                    {
                        e.Graphics.DrawString(" ● [RECORDING] ", myFont, Brushes.Red, new Point(2, 30));

                        if (this.showRecordMarkerCount == 15)
                        {
                            this.showRecordMarkerCount = 0;
                        }
                    }
                    this.showRecordMarkerCount++;
                }
            }

        }

        /// <summary>
        /// Videoes the player_ new frame.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="image">The image.</param>
        void videoPlayer_NewFrame(object sender, ref Bitmap image)
        {
            try
            {
                
                if (!this.isResolutionSet)
                {
                    // this is run once to set the resolution for the VideoRecorder
                    this.Width = image.Width;
                    this.Height = image.Height;
                    this.isResolutionSet = true;
                }

                //displays the image to the Camera IP Form
                this.display.Image = (Bitmap)image.Clone(); 

                if (motiondetect != null)
                {
                    if (this.MotionDetection && !this.motionDetected)
                    {
                        float motionLevel = motiondetect.ProcessFrame(image);
                        if (motionLevel > motionAlarmLevel)
                        {
                            if (this.calibrateAndResume > 5)
                            {
                                // if motion was detected in 5 subsequent frames
                                Thread th = new Thread(MotionReaction);
                                // start the motion reaction thread
                                th.Start(); 
                            }
                            else this.calibrateAndResume++;
                        }
                    }

                    if (IsRecording)
                    {
                        // if recording is enabled we enqueue the current frame to be encoded to a video file
                        Graphics gr = Graphics.FromImage(image);
                        Pen p = new Pen(Color.Red);
                        p.Width = 5.0f;
                        using (Font myFont = new Font("Tahoma", 10, FontStyle.Regular))
                        {
                            gr.DrawString(DateTime.Now.ToString(), myFont, Brushes.Red, new Point(15, 15));
                        }
                        frames.Enqueue((Bitmap)image.Clone());
                    }
                }
            }
            catch (Exception)
            { }
            
        }

        /// <summary>
        /// Motions the reaction.
        /// </summary>
        private void MotionReaction()
        {
            this.motionDetected = true;
            if (this.RecordOnMotion)
            {
                // record if Autorecord is toggled
                this.StartRecording(); 
            }
            if (this.BeepOnMotion)
            {
                // beep if BeepOnMotion is toggeled
                System.Console.Beep(BeepFreq, BeepDura);
            }

            // the user is notified for n seconds
            Thread.Sleep((AutoNotifyStop * 1000)); 
            
            calibrateAndResume = 0;
            this.motionDetected = false;

            // recording will stop for n seconds
            Thread.Sleep((AutoRecordStop * 1000));

            // the thread waits for seconds if there is no motion detected we stop the AutoRecord
            if (!this.forceRecord && this.motionDetected == false)
            {
                this.StopRecording();
            }
        }

        /// <summary>
        /// Does the record.
        /// </summary>
        private void DoRecord()
        {
            // we set our VideoFileWriter as well as the file name, resolution and fps
            VideoFileWriter writer = new VideoFileWriter();
            writer.Open(RecordingPath + "\\" + URL + String.Format("{0:_dd-M-yyyy_hh-mm-ss}", DateTime.Now) + ".avi", this.Width, this.Height, 20);

            // as long as we're recording
            // we dequeue the BitMaps waiting in the Queue and write them to the file
            while (IsRecording)
            {
                if (frames.Count > 0)
                {
                    Bitmap bmp = frames.Dequeue();
                    writer.WriteVideoFrame(bmp);
                }
            }
            writer.Close();
        }

        /// <summary>
        /// Starts the recording.
        /// </summary>
        public void StartRecording()
        {
            if (!IsRecording)
            {
                //// if were not already recording we start the recording thread
                this.IsRecording = true;
                Thread th = new Thread(DoRecord);
                th.Start();
            }
        }

        /// <summary>
        /// Stops the recording.
        /// </summary>
        public void StopRecording()
        {
            this.IsRecording = false;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose();
        }
        #endregion
    }
}
